/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.bff.designtime.generator.entitydataconvertor.method;

import com.inspur.edp.bef.bizentity.GspBizEntityElement;
import com.inspur.edp.bff.designtime.generator.common.GenUtils;
import com.inspur.edp.bff.designtime.generator.entitydataconvertor.VMObjectDataConvertorGenContext;
import com.inspur.edp.caf.generator.baseInfo.ParameterInfo;
import com.inspur.edp.caf.generator.baseInfo.TypeInfo;
import com.inspur.edp.caf.generator.method.ClassMethodGenerator;
import com.inspur.edp.caf.generator.utils.ExpressionUtils;
import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.element.GspElementObjectType;
import com.inspur.edp.cef.entity.entity.IEntityData;
import com.inspur.edp.cef.generator.DotNetToJavaStringHelper;
import com.inspur.edp.formserver.viewmodel.GspViewModelElement;
import com.inspur.edp.udt.designtime.api.entity.UnifiedDataTypeDef;
import lombok.var;
import org.eclipse.jdt.core.dom.*;

import java.util.ArrayList;
import java.util.Arrays;

public class MappingEntityDataPropertiesGenerator extends ClassMethodGenerator {

  private static final String ParamTransBeData = "_beData";
  private static final String ParamBeData = "beData";
  private static final String ParamTransVoData = "_vmData";
  private static final String ParamVoData = "vmData";

  private VMObjectDataConvertorGenContext parentGenContext;

  public MappingEntityDataPropertiesGenerator(VMObjectDataConvertorGenContext parentGenContext) {
    this.parentGenContext = parentGenContext;
  }

  @Override
  protected Block buildMethodBody() {
    Block blockSyntax = getTypeAsBlock();
    for (IGspCommonField field : parentGenContext.getVmObject().getContainElements()) {
      GspViewModelElement item = (GspViewModelElement) field;

      if (item.getIsVirtual()) {
        continue;
      }
      if (DotNetToJavaStringHelper.isNullOrEmpty(item.getMapping().getTargetObjId()))
      //TODO 异常封装
      {
        throw new RuntimeException(
            "#GspBefError# 字段" + item.getCode() + " 的Mapping字段ID不允许为空#GspBefError#");
      }

      if (item.getMapping().getTargetMetadataId().equals(parentGenContext.getVmGeneratorContext()
          .getVm().getMapping().getTargetMetadataId()) == false) {
        continue;
      }

      Object tempVar = parentGenContext.getTargetObject()
          .findElement(item.getMapping().getTargetObjId());
      GspBizEntityElement entityElement = (GspBizEntityElement) (
          (tempVar instanceof GspBizEntityElement) ? tempVar : null);
      if (entityElement == null) {
        continue;
      }

      addElementMapBlock(item, entityElement, blockSyntax);
    }
    return blockSyntax;
  }

  private Block getTypeAsBlock() {

    Block block = ast.newBlock();
    String beTypeName = null;
    TypeInfo beType = parentGenContext.getBEDataType(beTypeName);
    addImport(beType.getTypeFullName());
    String voType = parentGenContext.getVmObject().getGeneratedEntityClassInfo().getClassName();

    Statement beDataStatement = getTransDataStatement(beType.getTypeName(), ParamTransBeData,
        ParamBeData);
    Statement voDataStatement = getTransDataStatement(voType, ParamTransVoData, ParamVoData);

    block.statements().add(beDataStatement);
    block.statements().add(voDataStatement);

    return block;
  }

  private Statement getTransDataStatement(String typeName, String varName, String paramName) {

    VariableDeclarationFragment frag = ast.newVariableDeclarationFragment();
    frag.setName(ast.newSimpleName(varName));
    CastExpression castStatement = ast.newCastExpression();
    castStatement.setType(ast.newSimpleType(ast.newSimpleName(typeName)));
    castStatement.setExpression(ast.newSimpleName(paramName));
    frag.setInitializer(castStatement);

    VariableDeclarationStatement statement = ast.newVariableDeclarationStatement(frag);
    statement.setType(ast.newSimpleType(ast.newSimpleName(typeName)));

    return statement;
  }

  private void addElementMapBlock(GspViewModelElement vmElement, GspBizEntityElement beElement,
      Block blockSyntax) {
    if (vmElement.getIsUdt()) {
      addUdtElementMapBlock(vmElement, beElement, blockSyntax);
      return;
    }

    switch (vmElement.getObjectType()) {
      case None:
      case DynamicProp:
        addBasicElementMapBlock(vmElement, beElement, blockSyntax);
        break;
      case Association:
        addAssElementMapBlock(vmElement, beElement, blockSyntax);
        break;
      case Enum:
        addEnumElementMapBlock(vmElement, beElement, blockSyntax);
        break;
      default:
        throw new RuntimeException(
            "#GspBefError# 错误的类型" + vmElement.getObjectType().toString() + " #GspBefError# ");
    }
  }

  private void addUdtElementMapBlock(GspViewModelElement vmElement, GspBizEntityElement beElement,
      Block blockSyntax) {
    if (vmElement.getObjectType() == GspElementObjectType.Association && vmElement
        .containRefElementNotFromAssoUdt()) {
      addAssElementMapBlock(vmElement, beElement, blockSyntax);
    } else {
      addBasicElementMapBlock(vmElement, beElement, blockSyntax);
    }
  }

  private void addBasicElementMapBlock(GspViewModelElement vmElement, GspBizEntityElement beElement,
      Block blockSyntax) {

    MethodInvocation invocation = ExpressionUtils
        .getMethodInvocation(ast, ParamTransVoData, "set" + vmElement.getLabelID());

    invocation.arguments().add(
        ExpressionUtils.getMethodInvocation(ast, ParamTransBeData, "get" + beElement.getLabelID()));

    blockSyntax.statements().add(ast.newExpressionStatement(invocation));
  }


  private void addAssElementMapBlock(GspViewModelElement vmElement, GspBizEntityElement beElement,
      Block blockSyntax) {
    ifNullBlock(vmElement, beElement, blockSyntax);
  }

  private void ifNullBlock(GspViewModelElement vmElement, GspBizEntityElement beElement,
      Block blockSyntax) {

    IfStatement ifStatement = ast.newIfStatement();
    InfixExpression condition = ast.newInfixExpression();
    MethodInvocation leftStatement = ExpressionUtils
        .getMethodInvocation(ast, ParamTransBeData, "get" + beElement.getLabelID());

    condition.setLeftOperand(leftStatement);
    condition.setRightOperand(ast.newNullLiteral());
    condition.setOperator(InfixExpression.Operator.EQUALS);
    ifStatement.setExpression(condition);

    Block thenBlock = ast.newBlock();
    MethodInvocation thenInvocation = ExpressionUtils
        .getMethodInvocation(ast, ParamTransVoData, "set" + vmElement.getLabelID());
    thenInvocation.arguments().add(ast.newNullLiteral());
    thenBlock.statements().add(ast.newExpressionStatement(thenInvocation));

    ifStatement.setThenStatement(thenBlock);

    Block elseblock = buildAssElseStatement(vmElement, beElement);
    ifStatement.setElseStatement(elseblock);
    blockSyntax.statements().add(ifStatement);
  }

  private Block buildAssElseStatement(GspViewModelElement vmElement,
      GspBizEntityElement beElement) {
    Block block = ast.newBlock();
    if (vmElement.getIsUdt() && vmElement.containRefElementNotFromAssoUdt()) {

      String valName = "beAssoInfo";
      Statement getAssoInfoStatement = getAssoInfoStatement(beElement, valName);
      ExpressionStatement udtStatement = getUdtStatement(vmElement, beElement, valName);

      block.statements().add(getAssoInfoStatement);
      block.statements().add(udtStatement);
    } else {
      ExpressionStatement initStatement = addNewBlock(vmElement);
      ExpressionStatement keyStatement = getAssKeyStatement(vmElement, beElement);
      block.statements().add(initStatement);
      block.statements().add(keyStatement);
    }
    assPropMapBlock(vmElement, beElement, block);
    return block;
  }

  private Statement getAssoInfoStatement(GspBizEntityElement beElement, String valName) {

    String assoTypeName = null;
    TypeInfo assoType = GenUtils
        .GetBEAssoType(parentGenContext.getVmGeneratorContext(), beElement, assoTypeName);

    VariableDeclarationFragment frag = ast.newVariableDeclarationFragment();
    frag.setName(ast.newSimpleName(valName));
    MethodInvocation invocation = ExpressionUtils
        .getMethodInvocation(ast, ParamTransBeData, "get" + beElement.getLabelID());
    invocation = ExpressionUtils
        .getMethodInvocation(ast, invocation, "get" + beElement.getLabelID());

    frag.setInitializer(invocation);

    VariableDeclarationStatement statement = ast.newVariableDeclarationStatement(frag);
    statement
        .setType(ast.newSimpleType(ast.newQualifiedName(ast.newName(assoType.getTypePackageName()),
            ast.newSimpleName(assoType.getTypeName()))));
    return statement;
  }

  private ExpressionStatement addNewBlock(GspViewModelElement vmElement) {

    String assType = vmElement.getAssociationTypeName();

    MethodInvocation methodInvocation = ast.newMethodInvocation();

    methodInvocation.setExpression(ast.newSimpleName(ParamTransVoData));
    methodInvocation.setName(ast.newSimpleName("set" + vmElement.getLabelID()));
    ClassInstanceCreation creation = ast.newClassInstanceCreation();
    creation.setType(ast.newSimpleType(ast.newSimpleName(assType)));
    methodInvocation.arguments().add(creation);

    return ast.newExpressionStatement(methodInvocation);
  }


  private ExpressionStatement getUdtStatement(GspViewModelElement vmElement,
      GspBizEntityElement beElement, String valName) {

    String associatTypeName = vmElement.getAssociationTypeName();

    MethodInvocation incocation = ExpressionUtils
        .getMethodInvocation(ast, ParamTransVoData, "get" + vmElement.getLabelID());
    incocation = ExpressionUtils
        .getMethodInvocation(ast, incocation, "set" + beElement.getLabelID());

    ClassInstanceCreation arg = ast.newClassInstanceCreation();
    arg.setType(ast.newSimpleType(ast.newSimpleName(associatTypeName)));
    arg.arguments().add(ast.newSimpleName(valName));

    incocation.arguments().add(arg);

    return ast.newExpressionStatement(incocation);
  }

  private void assPropMapBlock(GspViewModelElement vmElement, GspBizEntityElement beElement,
      Block blockSyntax) {

    for (IGspCommonField refElement : vmElement.getChildAssociations().get(0)
        .getRefElementCollection()) {
      GspViewModelElement item = (GspViewModelElement) refElement;

      if (item.getIsFromAssoUdt()) {
        continue;
      }

      GspBizEntityElement beAssElement = (GspBizEntityElement) (
          (beElement.getChildAssociations().get(0).getRefElementCollection()
              .getItem(item.getMapping().getTargetObjId()) instanceof GspBizEntityElement)
              ? beElement.getChildAssociations().get(0).getRefElementCollection()
              .getItem(item.getMapping().getTargetObjId())
              : null);

      MethodInvocation voData = ExpressionUtils
          .getMethodInvocation(ast, "_vmData", "get" + vmElement.getLabelID());
      if (vmElement.getIsUdt()) {
        voData = ExpressionUtils.getMethodInvocation(ast, voData, "get" + beElement.getLabelID());
      }

      MethodInvocation beData = ExpressionUtils
          .getMethodInvocation(ast, "_beData", "get" + beElement.getLabelID());

      voData = ExpressionUtils.getMethodInvocation(ast, voData, "set" + item.getLabelID());
      beData = ExpressionUtils.getMethodInvocation(ast, beData, "get" + beAssElement.getLabelID());

      voData.arguments().add(beData);
      blockSyntax.statements().add(ast.newExpressionStatement(voData));
    }

  }

  private ExpressionStatement getAssKeyStatement(GspViewModelElement vmElement,
      GspBizEntityElement beElement) {

    MethodInvocation getterInvocation = ExpressionUtils
        .getMethodInvocation(ast, ParamTransVoData, "get" + vmElement.getLabelID());
    MethodInvocation invocation = ExpressionUtils
        .getMethodInvocation(ast, getterInvocation, "set" + vmElement.getLabelID());

    MethodInvocation argGetInvocation = ExpressionUtils
        .getMethodInvocation(ast, ParamTransBeData, "get" + beElement.getLabelID());
    MethodInvocation arg = ExpressionUtils
        .getMethodInvocation(ast, argGetInvocation, "get" + beElement.getLabelID());

    invocation.arguments().add(arg);

    return ast.newExpressionStatement(invocation);
  }

  /**
   * _vmData.EnumValue=(VMEnumType)(int)_beData.EnumValue;
   */
  private void addEnumElementMapBlock(GspViewModelElement vmElement, GspBizEntityElement beElement,
      Block blockSyntax) {
    IfStatement ifStatement = ast.newIfStatement();
    MethodInvocation leftOperan = ast.newMethodInvocation();
    leftOperan.setExpression(ast.newSimpleName(ParamTransBeData));
    leftOperan.setName(ast.newSimpleName("get" + vmElement.getLabelID()));
    InfixExpression infixExpression =  ast.newInfixExpression();
    infixExpression.setLeftOperand(leftOperan);
    infixExpression.setOperator(InfixExpression.Operator.EQUALS);
    infixExpression.setRightOperand(ast.newNullLiteral());
    ifStatement.setExpression(infixExpression);

    Block thenBlock = ast.newBlock();
    MethodInvocation thenMethod = ast.newMethodInvocation();
    thenMethod.setExpression(ast.newSimpleName(ParamTransVoData));
    thenMethod.setName(ast.newSimpleName("set" + vmElement.getLabelID()));
    thenMethod.arguments().add(ast.newNullLiteral());
    ExpressionStatement expressionStatement = ast.newExpressionStatement(thenMethod);
    thenBlock.statements().add(expressionStatement);
    ifStatement.setThenStatement(thenBlock);


    String enumType = vmElement.getEnumTypeName();

    MethodInvocation invocation = ast.newMethodInvocation();
    invocation.setExpression(ast.newSimpleName(ParamTransVoData));
    invocation.setName(ast.newSimpleName("set" + vmElement.getLabelID()));

    ArrayAccess arg = ast.newArrayAccess();

    MethodInvocation array = ast.newMethodInvocation();
    array.setExpression(ast.newSimpleName(enumType));
    array.setName(ast.newSimpleName("values"));
    arg.setArray(array);

    MethodInvocation index = ast.newMethodInvocation();
    MethodInvocation expression = ast.newMethodInvocation();
    expression.setExpression(ast.newSimpleName(ParamTransBeData));
    expression.setName(ast.newSimpleName("get" + beElement.getLabelID()));
    index.setExpression(expression);
    index.setName(ast.newSimpleName("ordinal"));
    arg.setIndex(index);

    invocation.arguments().add(arg);
    Block elseBlock = ast.newBlock();
    elseBlock.statements().add(ast.newExpressionStatement(invocation));
    ifStatement.setElseStatement(elseBlock);

    blockSyntax.statements().add(ifStatement);
  }

  @Override
  protected ArrayList<Modifier.ModifierKeyword> getAccessModifiers() {
    return new ArrayList<Modifier.ModifierKeyword>(Arrays.asList(
        new Modifier.ModifierKeyword[]{Modifier.ModifierKeyword.PROTECTED_KEYWORD}));
  }

  @Override
  protected boolean getIsOverride() {
    return true;
  }

  @Override
  protected String getMethodName() {
    return "mappingEntityDataProperties";
  }

  @Override
  protected ArrayList<ParameterInfo> getParameterCollection() {
    ArrayList<ParameterInfo> list = new ArrayList<ParameterInfo>();
    ParameterInfo paramBeData = new ParameterInfo();
    paramBeData.setParamType(new TypeInfo(IEntityData.class));
    paramBeData.setParamName(ParamBeData);
    list.add(paramBeData);
    ParameterInfo paramVoData = new ParameterInfo();
    paramVoData.setParamType(new TypeInfo(IEntityData.class));
    paramVoData.setParamName(ParamVoData);
    list.add(paramVoData);
    return list;
  }

  @Override
  protected TypeInfo getReturnType() {
    return new TypeInfo("void");
  }
}
